/*
 *  linux/fs/super.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * super.c contains code to handle the super-block tables.
 */
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>

#include <errno.h>
#include <sys/stat.h>

int sync_dev(int dev);
void wait_for_keypress(void);

/* set_bit uses setb, as gas doesn't recognize setc */
#define set_bit(bitnr,addr) ({ \
register int __res __asm__("ax"); \
__asm__("bt %2,%3;setb %%al":"=a" (__res):"a" (0),"r" (bitnr),"m" (*(addr))); \
__res; })

struct super_block super_block[NR_SUPER];
/* this is initialized in init/main.c */
int ROOT_DEV = 0;

static void lock_super(struct super_block * sb)
{
	cli();
	while (sb->s_lock)
		sleep_on(&(sb->s_wait));
	sb->s_lock = 1;
	sti();
}

static void free_super(struct super_block * sb)
{
	cli();
	sb->s_lock = 0;
	wake_up(&(sb->s_wait));
	sti();
}

static void wait_on_super(struct super_block * sb)
{
	cli();
	while (sb->s_lock)
		sleep_on(&(sb->s_wait));
	sti();
}

struct super_block * get_super(int dev)
{
	struct super_block * s;

	if (!dev)
		return NULL;
	s = 0+super_block;
	while (s < NR_SUPER+super_block)
		if (s->s_dev == dev) {
			wait_on_super(s);
			if (s->s_dev == dev)
				return s;
			s = 0+super_block;
		} else
			s++;
	return NULL;
}

void put_super(int dev)
{
	struct super_block * sb;
	struct m_inode * inode;
	int i;

	if (dev == ROOT_DEV) {
		printk("root diskette changed: prepare for armageddon\n\r");
		return;
	}
	if (!(sb = get_super(dev)))
		return;
	if (sb->s_imount) {
		printk("Mounted disk changed - tssk, tssk\n\r");
		return;
	}
	lock_super(sb);
	sb->s_dev = 0;
	for(i=0;i<I_MAP_SLOTS;i++)
		brelse(sb->s_imap[i]);
	for(i=0;i<Z_MAP_SLOTS;i++)
		brelse(sb->s_zmap[i]);
	free_super(sb);
	return;
}

static struct super_block * read_super(int dev)
{
	struct super_block * s;
	struct buffer_head * bh;
	int i,block;

	if (!dev)
		return NULL;
	check_disk_change(dev);
	if (s = get_super(dev))
		return s;
	for (s = 0+super_block ;; s++) {
		if (s >= NR_SUPER+super_block)
			return NULL;
		if (!s->s_dev)
			break;
	}
	s->s_dev = dev;
	s->s_isup = NULL;
	s->s_imount = NULL;
	s->s_time = 0;
	s->s_rd_only = 0;
	s->s_dirt = 0;
	lock_super(s);
	if (!(bh = bread(dev,1))) {
		s->s_dev=0;
		free_super(s);
		return NULL;
	}
	*((struct d_super_block *) s) =
		*((struct d_super_block *) bh->b_data);
	brelse(bh);
	if (s->s_magic != SUPER_MAGIC) {
		s->s_dev = 0;
		free_super(s);
		return NULL;
	}
	for (i=0;i<I_MAP_SLOTS;i++)
		s->s_imap[i] = NULL;
	for (i=0;i<Z_MAP_SLOTS;i++)
		s->s_zmap[i] = NULL;
	block=2;
	for (i=0 ; i < s->s_imap_blocks ; i++)
		if (s->s_imap[i]=bread(dev,block))
			block++;
		else
			break;
	for (i=0 ; i < s->s_zmap_blocks ; i++)
		if (s->s_zmap[i]=bread(dev,block))
			block++;
		else
			break;
	if (block != 2+s->s_imap_blocks+s->s_zmap_blocks) {
		for(i=0;i<I_MAP_SLOTS;i++)
			brelse(s->s_imap[i]);
		for(i=0;i<Z_MAP_SLOTS;i++)
			brelse(s->s_zmap[i]);
		s->s_dev=0;
		free_super(s);
		return NULL;
	}
	s->s_imap[0]->b_data[0] |= 1;
	s->s_zmap[0]->b_data[0] |= 1;
	free_super(s);
	return s;
}

int sys_umount(char * dev_name)
{
	struct m_inode * inode;
	struct super_block * sb;
	int dev;

	if (!(inode=namei(dev_name)))
		return -ENOENT;
	dev = inode->i_zone[0];
	if (!S_ISBLK(inode->i_mode)) {
		iput(inode);
		return -ENOTBLK;
	}
	iput(inode);
	if (dev==ROOT_DEV)
		return -EBUSY;
	if (!(sb=get_super(dev)) || !(sb->s_imount))
		return -ENOENT;
	if (!sb->s_imount->i_mount)
		printk("Mounted inode has i_mount=0\n");
	for (inode=inode_table+0 ; inode<inode_table+NR_INODE ; inode++)
		if (inode->i_dev==dev && inode->i_count)
				return -EBUSY;
	sb->s_imount->i_mount=0;
	iput(sb->s_imount);
	sb->s_imount = NULL;
	iput(sb->s_isup);
	sb->s_isup = NULL;
	put_super(dev);
	sync_dev(dev);
	return 0;
}

int sys_mount(char * dev_name, char * dir_name, int rw_flag)
{
	struct m_inode * dev_i, * dir_i;
	struct super_block * sb;
	int dev;

	if (!(dev_i=namei(dev_name)))
		return -ENOENT;
	dev = dev_i->i_zone[0];
	if (!S_ISBLK(dev_i->i_mode)) {
		iput(dev_i);
		return -EPERM;
	}
	iput(dev_i);
	if (!(dir_i=namei(dir_name)))
		return -ENOENT;
	if (dir_i->i_count != 1 || dir_i->i_num == ROOT_INO) {
		iput(dir_i);
		return -EBUSY;
	}
	if (!S_ISDIR(dir_i->i_mode)) {
		iput(dir_i);
		return -EPERM;
	}
	if (!(sb=read_super(dev))) {
		iput(dir_i);
		return -EBUSY;
	}
	if (sb->s_imount) {
		iput(dir_i);
		return -EBUSY;
	}
	if (dir_i->i_mount) {
		iput(dir_i);
		return -EPERM;
	}
	sb->s_imount=dir_i;
	dir_i->i_mount=1;
	dir_i->i_dirt=1;		/* NOTE! we don't iput(dir_i) */
	return 0;			/* we do that in umount */
}

void mount_root(void)
{
	int i,free;
    // 在 Linux 0.11 内核中，super_block 是一个关键数据结构，用于管理文件系统的超级块（Super Block）信息
	struct super_block * p;
	struct m_inode * mi;

    // 检查 inode 结构大小
    //  目的：确保磁盘上的 inode 结构（d_inode）大小为 32 字节，与内核预期一致。
    //  若不一致：触发内核崩溃（panic），防止数据解析错误。
	if (32 != sizeof (struct d_inode))
		panic("bad i-node size");
    // #define NR_FILE 64
    // 初始化文件表
    //  file_table：全局文件打开表，初始化所有文件的引用计数为 0。
    //  作用：准备文件系统状态，确保无残留的打开文件。
    // 这个 file_table 表示进程所使用的文件，进程每使用一个文件，都需要记录在这里，包括文件类型、文件 inode 索引信息等，
    // 而这个 f_count 表示被引用的次数，此时还没有引用，所以设置为零。
    // 而这个 file_table 的索引（当然准确说是进程的filp索引才是），就是我们通常说的文件描述符。比如有如下命令
    // echo "hello" > 0
    // 就表示把 hello 输出到 0 号文件描述符。
    // 0 号文件描述符是哪个文件呢？就是 file_table[0] 所表示的文件
    // 这个文件在哪里呢？注意到 file 结构里有个 f_inode 字段，通过 f_inode 即可找到它的 inode 信息，
    // inode 信息包含了一个文件所需要的全部信息，包括文件的大小、文件的类型、文件所在的硬盘块号，这个所在硬盘块号，就是文件的位置咯
    // struct file {
    //	unsigned short f_mode;
    //	unsigned short f_flags;
    //	unsigned short f_count;
    //	struct m_inode * f_inode;
    //	off_t f_pos;
    // };
	for(i=0;i<NR_FILE;i++)
		file_table[i].f_count=0;
    // 处理根设备为软盘的情况
    //  MAJOR(ROOT_DEV)：判断根设备是否是软盘（主设备号 2）。
    //  交互提示：若为软盘，要求用户插入磁盘并按键继续
	if (MAJOR(ROOT_DEV) == 2) {
		printk("Insert root floppy and press ENTER");
		wait_for_keypress();
	}
    // 初始化超级块数组
    // #define NR_SUPER 8
    //  super_block[NR_SUPER]：内核维护的超级块数组（NR_SUPER=8）。
    //  作用：重置所有超级块槽位，准备挂载。
    // 把一个数组 super_block 做清零工作。
    // 这个 super_block 存在的意义是，操作系统与一个设备以文件形式进行读写访问时，就需要把这个设备的超级块信息放在这里。
    // 这样通过这个超级块，就可以掌控这个设备的文件系统全局了。
	for(p = &super_block[0] ; p < &super_block[NR_SUPER] ; p++) {
		p->s_dev = 0; // 设备号清零（标记未使用）
		p->s_lock = 0; // 解锁超级块
		p->s_wait = NULL; // 清空等待队列
	}
    // 读取根设备的超级块
    // int ROOT_DEV = 0;
    // read_super(ROOT_DEV)：从根设备（ROOT_DEV）读取超级块到内存。
    //  内部流程：
    //  读取磁盘第一个块（超级块位置固定）。
    //  校验魔数（如 s_magic=0x137F 表示 MINIX 文件系统）。
    //  初始化内存中的 super_block 结构。
    //  失败处理：若超级块无效，触发 panic。
    // 读取硬盘中的超级块。
	if (!(p=read_super(ROOT_DEV)))
		panic("Unable to mount root");
    // 加载根目录的 inode
    // #define ROOT_INO 1
    // iget(ROOT_DEV, ROOT_INO)：获取根目录的 inode（inode 号固定为 1）。
    //  流程：
    //  根据 ROOT_INO=1 从磁盘读取 inode 数据。
    //  填充内存中的 struct m_inode 结构。
    //  失败处理：若 inode 加载失败，触发 panic
    // 读取根 inode 信息
    // 把该 inode 设置为当前进程（也就是进程 1）的当前工作目录和根目录。
	if (!(mi=iget(ROOT_DEV,ROOT_INO)))
		panic("Unable to read root i-node");
    // 设置超级块和进程的根目录
    //  引用计数：i_count=4（1 次为超级块引用，3 次为逻辑上的额外引用）。
    //  进程上下文：current->root 和 current->pwd 确保后续文件操作从根目录开始。
    // // 增加引用计数（逻辑上被多处引用）
	mi->i_count += 3 ;	/* NOTE! it is logically used 4 times, not 1 */
    // // 超级块指向根目录 inode
	p->s_isup = p->s_imount = mi;
    // // 当前进程的工作目录设为根
	current->pwd = mi;
    // // 当前进程的根目录设为根
	current->root = mi;
    // 统计空闲磁盘块和 inode
    // 空闲块统计：
    //  遍历块位图（s_zmap），统计未使用的块数。
    //  i&8191：计算位图中的偏移（8191=0x1FFF，13 位掩码）。
    //  i>>13：定位位图所在的块号（每块 8192 位）。

    // 空闲 inode 统计：
    //  类似地，遍历 inode 位图（s_imap）。
    //  输出结果：打印空闲块和 inode 数量（调试信息）
	free=0;
	i=p->s_nzones;
    // 记录块位图信息。
	while (-- i >= 0)
		if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))
			free++;
	printk("%d/%d free blocks\n\r",free,p->s_nzones);
	free=0;
	i=p->s_ninodes+1;
    // 记录 inode 位图信息。
	while (-- i >= 0)
		if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))
			free++;
	printk("%d/%d free inodes\n\r",free,p->s_ninodes);
}
